<?php
//session_start();
set_time_limit(0); // 关闭30秒执行脚本超时异常
/*
 * RedisAdminLite
 * 开源作品-PHP写的Redis管理工具(单文件绿色版)
 * Redis在线管理工具
 * 程序特性：
 *      1. 单文件绿色版,部署超简单
 *      2. 支持Redis大部分操作(持续完善中...)
 *      3. 根据Redis大数据量特性进行操作优化
 *      4. 支持用户登录验证、IP验证等多种权限验证功能
 *      5. 支持多个数据库服务器切换
 * 使用方法：
 *      1. 复制本文件到web任意目录
 *      2. 修改配置信息为实际内容
 *      3. 在浏览器输入链接路径,开始使用
 * 说明：写这个程序主要是为了辅助项目中用到的Redis开发.因为项目开发任务繁重,所以这个小程序暂时只能提供浏览、查询、添加、删除等功能(足以应付项目开发需要). 现在,要把精力转移到主项目开发中, 等以后有精力了再继续完善更多功能.如果您有兴趣一起完善这个小程序,欢迎使用git一起开发~感谢！
 * 项目周期：
 *      2015-8-13 立项开发
 *      2015-8-14 框架基本完成
 * 版权：不限商业用途! 如有修改代码请保留原著署名,并把修改后的代码回馈到作者邮箱：<14507247@qq.com>,促进本产品进步.做一个有公德心的正能量程序员~
 *      作者博客：http://www.cnblogs.com/sochishun/ 欢迎交流~
 * @version 1.0 <2015-8-13> SoChishun <14507247@qq.com> Added.
 * @version 1.1 <2015-10-8> SoChishun 新增键名列表对2.8.0以下版本的兼容,优化查询结果的显示效果.
 * @version 1.2 <2017-8-21> SoChishun 新增服务器密码验证功能和对PHP5.4以上版本的适配
 * @version 1.3 <2017-8-23> SoChishun html输出时新增htmlspecialchars过滤
 * @version 2.0 <2017-8-24> SoChishun 项目由SuRedisAdmin_PHP_1_0重构为RedisAdminLite，增加访问权限验证功能
 * @version 2.1 <2017-9-6> SoChishun 新增key_prefix选项
 * @version 2.2 <2017-9-18> SoChishun 侧边栏键名列表的title属性新增键值类型显示和剩余生存时间显示; 修正zset值显示错误的问题
 * @version 2.3 <2017-9-24> SoChishun 新增服务器环境标题前缀提醒功能; 解决脚本超时异常; 新增服务器端口转发rinetd配置功能;
 * @version 2.4 <2017-9-29> SoChishun 新增界面表格行隐藏功能和表格行点击后高亮功能
 * @version 2.5 <2017-10-10> SoChishun 重构代码,取消SuRedisAdmin类;优化hash类型的显示,改进界面布局,侧边栏增加搜索和跳转功能;$dbname传递由session改为url参数;
 * @version 2.6 <2017-10-12> SoChishun 数据库下拉框新增记录数量提示;完善hash和string类型项目的编辑功能,新增doset,dohset,dohdel,dosadd,dozadd方法;完善新增表单功能;
 * @version 2.7 <2017-10-13> SoChishun 新增服务器IP显示,改进端口映射判断逻辑,如果同服务器则自动忽略端口映射以提高性能;
 * @version 2.8 <2017-10-20> SoChishun 新增quick_keys常用快捷键名预设功能,搜索结果新增批量删除功能;下拉控件新增字符截短功能,优化界面显示;
 * @version 2.9 <2017-10-27> SoChishun 新增命令行功能
 * @version 3.0 <2017-11-6> SoChishun 为适应负载均衡多台服务器分布式使用场景,登录会话保持由单机的session存储改为url参数token;侧边栏搜索框增加鼠标点击三次自动填写*符号的功能
 * @version 3.1 <2017-11-28> SoChishun 改进token功能，增加续期功能，避免频繁超时重新登录的繁琐操作
 * @version 3.2 <2017-12-6> SoChishun 新增读取本地配置文件功能,如果有本地配置文件,则优先读取本地配置文件
 * @version 3.3 <2017-12-19> SoChishun 登录方法封装成JWTLite类,配置信息处理方法封装成ServerConfUtil类,方便管理和迁移
 * @version 3.4 <2018-1-12> SoChishun 引入ptshead.php头文件,简化代码,提高代码复用
 * @version 3.5 <2018-3-16> SoChishun 编辑界面新增列表或集合类型的行数统计功能
 * @see https://github.com/phpredis/phpredis
 */

// 版本号
define('VERSION', '3.5.0');
// 程序名称
define('APPNAME', 'RedisAdminLite');

// 引入头文件
include './pts_head.php';
// #JWTLite配置
// 页面访问验证配置
JWTLite::check_login();
list($loginid, $token, $loginexp) = array_values(JWTLite::$LOGIN_RESULT);

// 全局配置
$config = array(
    'page_size' => 60, // 侧边栏键名列表分页记录数量，如果脚本执行时间过长，可以适当减少分页记录数量
    'default_db' => 'db0', // 默认数据库
    'server' => array(// 多服务器设置
        /* 本地服务器 */
        'localhost' => array(
            'host' => '127.0.0.1',
            'port' => 6379,
            'key_prefix' => ['rdb1:'], // 键名前缀,用户精简侧边栏键名长度
            'is_prod' => false, // 是否正式生产环境,用于在界面上警示提醒
            'page_size' => 300, // 侧边栏键名列表分页尺寸
            'default_db' => 'db0', // 默认数据库
        ),
        /* 远程服务器-remote */
        'remote' => array(
            'host' => 'r-test.redis.rds.aliyuncs.com',
            'port' => 6379,
            'password' => 'Um2usfttLioarMpP',
            'key_prefix' => ['rdb01:', 'rdb02:'], // 键名前缀,用户精简侧边栏键名长度
            'is_prod' => false, // 是否正式生产环境,用于在界面上警示提醒
            'rinetd' => ['host' => '33.15.80.62', 'port' => 6399], // 端口映射服务器地址,如果配置该字段，则会解析时会替换host和port字段的值
            'conf_file' => ['path' => '../public/directory_init.php', 'map' => ['host' => 'RDB_CLIENT', 'password' => 'RDB_PASSWORD', 'port' => 'RDB_PORT', 'default_db' => 'REDIS_PREFIX_DB', 'key_prefix' => 'REDIS_PREFIX']], // 本地配置文件,path为路径, map为字段映射,如果本地文件存在,则以本地配置文件为主
        ),
    ),
    'current_server' => 'remote', // 指定当前活动的Redis服务器
);

// 连接redis
$redis = new Redis();
if (is_null($redis)) {
    die('Redis Create Failure!');
}

// 数据库配置解析
$server = ServerConfUtil::getCurrentServer($config);
$curServId = $server['current_id']; // 当前服务器ID
// 解析host和port
list($host, $port) = ServerConfUtil::parseHostAndPortFromServer($server);

// 默认数据库
$defaultDbName = 'db0';
if (!empty($config['default_db'])) {
    $defaultDbName = $config['default_db'];
}
if (!empty($server['default_db'])) {
    $defaultDbName = $server['default_db'];
    if (is_int($defaultDbName)) {
        $defaultDbName = 'db' . $defaultDbName;
    }
}
// 分页尺寸
$pageSize = 30;
if (!empty($config['page_size'])) {
    $pageSize = $config['page_size'];
}
if (!empty($server['page_size'])) {
    $pageSize = $server['page_size'];
}

$isProd = isset($server['is_prod']) && $server['is_prod']; // 是否正式生产环境
$titlePrefix = $isProd ? "[正式服$curServId] " : "[$curServId] "; // 页面标题前缀,显示当前服务器
$keyPrefixSet = isset($server['key_prefix']) ? $server['key_prefix'] : '';
if ($keyPrefixSet && !is_array($keyPrefixSet)) {
    $keyPrefixSet = array($keyPrefixSet);
}
$curKeyPrefix = $keyPrefixSet ? current($keyPrefixSet) : '';
try {
    /**
     * host: string. can be a host, or the path to a unix domain socket 
     * port: int, optional 
     * timeout: float, value in seconds (optional, default is 0 meaning unlimited) 
     * persistent_id: string. identity for the requested persistent connection 
     * retry_interval: int, value in milliseconds (optional) 
     * read_timeout: float, value in seconds (optional, default is 0 meaning unlimited)
     */
    $redis->connect($host, $port);
    if (!empty($server['password'])) {
        $redis->auth($server['password']);
    }
} catch (Exception $ex) {
    echo 'Redis Connect Failure:', $ex->getMessage();
    exit;
}

// 全局变量
$curDbName = input('db', $defaultDbName); // 当前数据库名称
$curKey = input('key'); // 键名
$action = input('action'); // 操作
$pageSize = input('pagesize', $pageSize);
$pageId = input('pageid', 1);

// 常用搜索键预配置 2017-10-20
$quickKeys = array(
    'livezb' => array('user:', 'user:mission*:' . date('Y-m-d'), 'video:', 'xiacai:', 'xiacai_guessing:', 'guessing_log:'),
    'starzb' => array('user:', 'user:mission*:' . date('Y-m-d'), 'video:', 'xiacai:', 'xiacai_guessing:', 'guessing_log:'),
);
?>
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title><?php echo $titlePrefix, ($curKey ? short_text($curKey, 16, true) . ' - ' : ''), APPNAME, ' - ', VERSION ?></title>
        <style type="text/css">
            body, td, th{font-size:13px; color:#333;}
            aside {width:210px; float:left;}
            fieldset {margin-bottom:5px;}
            aside fieldset div { max-height: 600px; overflow-y: auto; overflow-x:hidden; width:180px;}
            aside li a:last-child {display:inline-block; width:130px;overflow: hidden;white-space: nowrap; text-overflow: ellipsis;}
            a{text-decoration: none;}
            h1 a { color:#555; font-weight: normal;}
            ul{margin:0;}
            li{list-style: none; margin-left: -40px;}
            li.active { font-weight: bold; color:#F00;}
            li.active a{ color:#F00;}
            main {width: 960px; float:left; padding-top: 5px;}
            footer { clear: left; padding:10px; text-align: right; color:#666; border-top:solid 2px #CCC; margin-top:5px;}
            a {text-decoration: none;}
            main h3 { margin: 5px 0;}
            h3 a { font-weight: normal; font-size:12px;}
            .red { color:#F00;}
            .green { color:#090;}
            aside h3 { margin:5px 0px;}
            .content { max-height: 680px; overflow-y:auto; margin-bottom: 5px;}
            .search_result{max-height:540px; overflow-y:auto; background-color:#efefef; margin-top: 3px; padding: 5px; line-height: 16px;}
            .grid { border:solid 1px #CCC; border-bottom:none; border-right: none; width:100%}
            .grid tr:hover{background-color: #efefef;}
            .grid tr.active{background-color: #ffc;}
            .grid th, .grid td { border:solid 1px #CCC; border-left:none; border-top:none; padding:3px; word-break:break-all; text-align: left;}
            .grid td input, .grid td textarea { width:99%}
            .propertyinfo td { width:360px; word-break: break-all;}
            .propertyinfo th { word-break: keep-all; padding:0;}
            a.lnk-hide { font-weight: normal; color:#999;}
        </style>
    </head>
    <body>
        <script src="http://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
        <?php
        echo '<h1>', APPNAME, ' ';
        echo '<a href="?new-window" title="新开一个页面" target="_blank" style="font-size:12px;">[新窗口]</a>';
        echo '<a href="?reset" title="重置当前页面" style="font-size:12px;">[重置]</a>';
        echo '<a href="#" title="刷新当前页面" style="font-size:12px;" onclick="location.reload()">[刷新]</a>';
        echo '</h1>';
        // 用户登录信息
        if (isset($loginid)) {
            echo '<span title="登录过期时间：', $loginexp, ' (超时前进行跳转操作能让登录会话自动延期,避免重登录操作~)">', $loginid, '</span>';
            echo ' [<a href="?action=logout" onclick="return confirm(\'您确定要注销登录吗?\');">注销登录</a>]';
        }
        echo ' 客户端IP:', $_SERVER['REMOTE_ADDR'], ' <span style="display:none">服务器IP:' . JWTLite::get_host_ip(), ' 连接地址:<span title="', $host, ':', $port, '">', $host, '</span></span> <a href="#" id="lnk-toggle-ipinfo">[...]</a>';
        ?>
        <fieldset style="max-width:1115px;">
            <legend>Redis状态信息(<strong style="color:#F00"><?php echo $curServId ?></strong>) <a href="#" class="lnk-switch">[+]</a></legend>
            <?php
            $dbs = [];
            $info = $redis->info(); // 获取Redis状态信息 2015-8-13 SoChishun Added.
            echo '<div class="fieldset_content" style="max-height:250px; overflow:auto;display:none;">';
            echo '<table cellpadding="0" cellspacing="0" class="grid propertyinfo"><tr>';
            $i = 0;
            foreach ($info as $skey => $svalue) {
                if ($i > 0 && $i % 3 < 1) {
                    echo '</tr><tr>';
                }
                // 解析数据库id
                if (0 === strpos($skey, 'db')) {
                    $avals = explode(',', $svalue);
                    foreach ($avals as $sval) {
                        $atmp = explode('=', $sval);
                        $dbs[$skey][$atmp[0]] = $atmp[1];
                    }
                }
                // 解析版本号
                if ('redis_version' == $skey) {
                    $redis_version = $svalue;
                }
                echo '<th>', htmlspecialchars($skey), '</th><td>', htmlspecialchars($svalue), '</td>';
                $i++;
            }
            echo '</tr></table>';
            echo '</div>';
            ?>
        </fieldset>
        <aside>
            <?php
// 选择数据库 2015-8-14 SoChishun Added.
            $dbconfig = [];
            if (array_key_exists($curDbName, $dbs)) {
                $dbconfig = $dbs[$curDbName]; // [全局变量] 当前数据库信息 array('db'=>'','db_id'=>0,'keys'=>0,'expires'=>0,'avg_ttl'=>0);
                $dbconfig['db'] = $curDbName;
                $dbconfig['db_id'] = substr($curDbName, 2);
                $redis->select($dbconfig['db_id']);
            } else {
                echo '服务器 [' . $curDbName . '] 不存在!';
            }

            echo '<div style="margin:3px 0;">数据库<span title="当前服务器共有', count($dbs), '个数据库">[*', count($dbs), ']</span>：<select id="slt-db"><option value="">选择数据库</option>';
            foreach ($dbs as $skey => $avalue) {
                echo '<option value="', $skey, '"', ($curDbName == $skey ? ' selected="selected"' : ''), ' title="', $skey, '共有', $avalue['keys'], '个键" data-url="?db=', $skey, '&token=', $token, '">', short_text($skey, 11), ' [*', $avalue['keys'], ']</option>';
            }
            echo '</select></div>';
            // 数据库操作下拉框
            $dboptActions = [
                'showaddstring' => '新增String键',
                'showaddhash' => '新增Hash键',
                'showaddlist' => '新增List键',
                'showaddset' => '新增Set键',
                'showaddzset' => '新增ZSet键',
                'doflushdb' => '清空数据库' . $curDbName,
            ];
            echo '<div>操作：<select id="slt-dbopt"><option value="">==选择操作==</option>';
            foreach ($dboptActions as $skey => $stitle) {
                echo '<option value="', $skey, '" title="', $stitle, '"', ($action == $skey ? ' selected="selected"' : ''), ' data-url="?db=', $curDbName, '&action=', $skey, '&token=', $token, '">', $stitle, '</option>';
            }
            echo '</select></div>';
            ?>
            <fieldset>
                <legend>搜索：<a href="#" class="lnk-switch">[-]</a></legend>
                <div class="fieldset_content">
                    <form action="?action=search">
                        <span class="red">模糊搜索:*words*</span>
                        <textarea id="search-key" name="key" title="<?php echo $curKey ?>" cols="22" rows="2" placeholder="双击输入框2次末尾自动补全*号"><?php echo input('key', $curKeyPrefix) ?></textarea>
                        <?php
                        // 通用快捷键名辅助
                        if (!empty($quickKeys)) {
                            foreach ($quickKeys as $ckhost => $ckkeys) {
                                if ($ckhost == $curServId) {
                                    echo '<div style="margin-bottom:3px;">';
                                    echo '<select id="slt-quick-key"><option value="">==常用键名==</option>';
                                    foreach ($ckkeys as $ckkey) {
                                        echo '<option value="', $curKeyPrefix, $ckkey, '" title="', $ckkey, '">', short_text($ckkey, 20), '</option>';
                                    }
                                    echo '</select></div>';
                                }
                            }
                        }
                        ?>
                        <div><button type="submit" title="立即搜索">搜索</button> <button type="button" id="btn-go" title="跳转到键名" data-url="?db=<?php echo $curDbName ?>&action=showedit&key=varkey&token=<?php echo $token ?>">转到</button> <button type="reset">重置</button></div>
                        <input type="hidden" name="action" value="search" />
                        <input type="hidden" name="db" value="<?php echo $curDbName ?>" />
                        <input type="hidden" name="token" value="<?php echo $token ?>" />
                    </form>
                </div>
            </fieldset>
            <fieldset>
                <legend>键名：</legend>
                <div>
                    <ul>
                        <?php
                        // 获取当前数据库所有键值
                        if ($dbconfig) { // 如果redis数据库不是空的则显示键名
                            $pagecount = ceil($dbconfig['keys'] / $pageSize);
                            show_aside_keys($redis, $pageId, $pageSize, $redis_version, $curKey, $keyPrefixSet, $curDbName, $token);
                        }
                        ?>
                    </ul>
                </div>
                <div class="pager" style="max-width: 180px; word-break: break-all; margin-top:5px;" title="手动修改浏览器地址栏参数pagesize可以改变分页尺寸">
                    <?php
                    if ($dbconfig) {
                        // 分页功能 2015-8-14 SoChishun Added.
                        echo '共', $dbconfig['keys'], '行,每页', $pageSize, '行, 页次', $pageId, '/', $pagecount, '<br />页码：';
                        for ($i = 1; $i <= $pagecount; $i++) {
                            echo '<a href="?db=', $curDbName, '&pagesize=', $pageSize, '&pageid=', $i, '&token=', $token, '" title="转到第', $i, '页">', $i, '</a> ';
                        }
                    }
                    ?>
                </div>
        </aside>
        <main>
            <?php
            // 显示命令行表单
            show_command_form($curDbName, input('cmd'), $token);
            if ($action == 'docommand') {
                echo '<div style="margin:5px 0">执行结果：<a href="?db=', $curDbName, '&token=', $token, '">[刷新]</a></div>';
                action_command($redis, $curDbName, input('cmd'));
            }
// 表单操作 2015-8-13 SoChishun Added.
            if ($action) {
                switch ($action) {
                    // 视图操作
                    case 'showaddstring':
                        show_add_form($curDbName, 'string', $token);
                        break;
                    case 'showaddhash':
                        show_add_form($curDbName, 'hash', $token);
                        break;
                    case 'showaddlist':
                        show_add_form($curDbName, 'list', $token);
                        break;
                    case 'showaddset':
                        show_add_form($curDbName, 'set', $token);
                        break;
                    case 'showaddzset':
                        show_add_form($curDbName, 'zset', $token);
                        break;
                    case 'showedit':
                        show_edit_form($redis, $curKey, $curDbName, $token);
                        break;
                    case 'search':
                        show_search_form($redis, $curKey, $curKeyPrefix, $curDbName, $token);
                        break;
                    // 动作操作
                    case 'dodelete':
                        action_delete($redis, $curKey, $curDbName, $token);
                        break;
                    case 'dorename': // rename key
                        action_dorename($redis, $curKey, input('newkey'), $curDbName, $token);
                        break;
                    case 'doset': // string set
                        action_doset($redis, $curKey, input('value'), $curDbName, $token);
                        break;
                    case 'dohset': // hash hset
                        action_dohset($redis, $curKey, input('field'), input('value'), $curDbName, $token);
                        break;
                    case 'dohdel': // hash hdel
                        action_dohdel($redis, $curKey, input('field'), $curDbName, $token);
                        break;
                    case 'dorpush': // list rpush
                        action_dorpush($redis, $curKey, input('value'), $curDbName, $token);
                    case 'dosadd': // set sadd
                        action_dosadd($redis, $curKey, input('value'), $curDbName, $token);
                    case 'dozadd': // set sadd
                        action_dozadd($redis, $curKey, input('score'), input('value'), $curDbName, $token);
                    case 'doflushdb':
                        if ($curDbName) {
                            $db_id = substr($curDbName, 2);
                            $redis->select($db_id);
                            $redis->flushDB();
                            redirect(true, "清空数据库[$curDbName]成功!", "?db=$curDbName&token=$token");
                        } else {
                            redirect(true, "数据库[$curDbName]不存在!", "?db=$curDbName&token=$token");
                        }
                        break;
                }
            }
            ?>
        </main>
        <div style="clear: both"></div>
        <footer>
            Copyright (c) <?php echo date('Y'), ' ', APPNAME ?>;
        </footer>
        <script>
            // 2017-10-28 服务器信息改为显隐控制，提高安全性，防止被窥探
            $('#lnk-toggle-ipinfo').click(function () {
                var $a = $(this);
                if ($a.text() == '[...]') {
                    $a.text('[x]').prev().show();
                } else {
                    $a.text('[...]').prev().hide();
                }
                return false;
            })
            // 展开或收起fieldset内容
            $('legend .lnk-switch').click(function () {
                var 　 $a = $(this);
                if ($a.text() == '[-]') {
                    $a.text('[+]').attr('title', '展开');
                    $a.parent().parent().find('.fieldset_content').hide();
                } else {
                    $a.text('[-]').attr('title', '收起');
                    $a.parent().parent().find('.fieldset_content').show();
                }
            })
            // 数据库切换
            $('#slt-db').change(function () {
                var val = $(this).val();
                if (val.length > 0) {
                    location.href = $(this).find(':selected').data('url');
                }
            })
            // 数据库操作切换
            $('#slt-dbopt').change(function () {
                var val = $(this).val();
                if (val.length < 1) {
                    return;
                }
                var db = $(this).data('db');
                if (val == 'doflushdb') {
                    if (!confirm('您确定要清空数据库中[' + db + ']所有数据吗?此操作不可复原!')) {
                        return;
                    }
                }
                location.href = $(this).find(':selected').data('url');
            })
            // 2017-11-6 鼠标点击三次自动追加*符号
            $('#search-key').click(function () {
                var $input = $(this);
                var val = $input.val();
                if (val.indexOf('*') > -1) {
                    return;
                }
                var n = $input.data('clickCount');
                if (!n) {
                    n = 0;
                }
                n++;
                if (n % 3 == 0) {
                    n = 0;
                    $input.val(val + '*');
                }
                $input.data('clickCount', n);
            })
            $('#slt-quick-key').change(function () {
                var val = $(this).val();
                if (val.length < 1) {
                    return;
                }
                if (val.indexOf('*') < 0) {
                    val = val + '*';
                }
                $('#search-key').val(val);
            })
            // 直接跳转到指定的键名 2017-10-11
            $('#btn-go').click(function () {
                var val = $('#search-key').val();
                if (val) {
                    var url = $(this).data('url');
                    location.href = url.replace('varkey', val);
                }
                return false;
            });
            // 选中表格行突出背景色
            $('.grid tr').click(function () {
                if ($(this).hasClass('unhover')) {
                    return;
                }
                $(this).toggleClass('active');
            })
            // 隐藏表格行
            $('.lnk-hide-v').click(function () {
                $(this).parentsUntil('tr').parent().hide().next().hide();
                return false;
            })
            $('.lnk-hide').click(function () {
                $(this).parentsUntil('tr').parent().hide();
                return false;
            })
            $('.lnk-show').click(function () {
                $(this).parent().next().find('tr').show();
                return false;
            })
            // 批量删除
            $('#btn-del-checked').click(function () {
                if (!confirm('您确定要执行[删除所选]的操作吗?')) {
                    return false;
                }
                var keys = [];
                var i = 0;
                $('.search_result input:checked').each(function () {
                    keys[i] = this.value;
                    i++;
                })
                var url = $(this).data('url');
                location.href = url.replace('varkey', keys);
            })
            $('#btn-del-all').click(function () {
                if (!confirm('您确定要执行[删除全部]操作吗?')) {
                    return false;
                }
                var url = $(this).data('url');
                location.href = url;
            })
        </script>
        <script>
            // 重命名表单提交 2017-10-12
            function fn_rename_submit(btn) {
                var $a = $(btn);
                var $input = $a.prev();
                if (is_empty($input.val())) {
                    alert('键名不能为空');
                    return false;
                }
                var val = $.trim($input.val());
                if (val == $input.data('value')) {
                    alert('键名无变化,无需操作!');
                    return false;
                }
                btn.href = btn.href.replace('varnewkey', val)
            }
            // hset提交 2017-10-12
            function fn_hset_submit(btn) {
                if (!confirm('您确定要执行此操作吗?')) {
                    return false;
                }
                var $a = $(btn);
                var $tr = $a.parentsUntil('tr').parent();
                var field = $tr.find('[name="field"]').val();
                var value = $tr.find('[name="value"]').val();
                if (is_empty(field)) {
                    alert('Field不能为空');
                    return false;
                }
                btn.href = btn.href.replace('varfield', field).replace('varvalue', value);
            }
            // 判断字符串是否是空格 2017-10-12
            function is_empty(str) {
                if (typeof (str) == 'undefined' || null == str || str.length == 0) {
                    return true;
                }
                var regu = "^[ ]+$";
                var re = new RegExp(regu);
                return re.test(str);
            }
        </script>
    </body>
</html>
<?php
/**
 * 业务类
 * @version 1.0 <2015-8-13> SoChishun Added.
 */

/**
 * 显示边栏键值列表视图
 * @param Redis $redis Redis实例对象
 * @param integer $pageId 分页id
 * @param integer $pagesize 分页尺寸
 * @param string $version redis版本号
 * @param string $curkey 当前输入的键名
 * @param string $key_prefix
 * @param string $dbname
 * @param string $token
 * @version 1.0 <2015-8-14> SoChishun Added.
 * @version 2.0 <2015-10-8> SoChishun 新增对2.8.0之前版本的兼容方法
 * @version 2.1 <2017-8-25> SoChishun $pageconf参数重构为$pageid,$pagesize，增加$curkey参数
 * @version 2.2 <2017-9-6> SoChishun 新增key_prefix选项
 * @version 2.3 <2017-9-18> 新增键类型的显示,剩余生存时间显示
 */
function show_aside_keys($redis, $pageId, $pagesize, $version, $curkey, $key_prefix, $dbname, $token) {
    if (!is_numeric($pageId) || !is_numeric($pagesize)) {
        die('分页参数无效');
    }
    $row_start = ($pageId - 1) * $pagesize;
    $row_end = $pageId * $pagesize;
    if (version_compare('2.8.0', $version) < 0) {
        // 2.8.0以下版本适用
        show_keys_keys($redis, $row_start, $row_end, $curkey, $key_prefix, $dbname, $token);
    } else {
        // 2.8.0以上版本适用
        show_keys_scan($redis, $row_start, $row_end, $curkey, $key_prefix, $dbname, $token);
    }
}

/**
 * keys方法获取键名
 * <br />2.8.0以下版本适用
 * @param Redis $redis
 * @param integer $row_start
 * @param integer $row_end
 * @param string $curkey 当前输入的键名
 * @param string $key_prefix
 * @param string $dbname
 * @param string $token
 * @version 1.0 <2015-10-8> SoChishun Added.
 */
function show_keys_keys($redis, $row_start, $row_end, $curkey, $key_prefix, $dbname, $token) {
    //ob_end_clean(); 服务器启用了PHP缓存，这里要注释，否则会版面会错乱
    $akeys = $redis->keys('*');
    if ($akeys) {
        $i = 1;
        $text = '';
        $title = '';
        $typenames = array(0 => 'NOT_FOUND', 1 => 'STRING', 2 => 'SET', 3 => 'LIST', 4 => 'REDIS_ZSET', 5 => 'HASH');
        foreach ($akeys as $skey) {
            if ($i > $row_end) {
                break;
            }
            if ($i >= $row_start) {
                $len = 20 - strlen($i);
                $text = $skey;
                if ($key_prefix) {
                    foreach ($key_prefix as $prefix) {
                        if (0 === strpos($skey, $prefix)) {
                            $key_prefix_len = strlen($prefix);
                            $text = ($key_prefix_len > 0) ? substr($skey, $key_prefix_len) : $skey;
                            break;
                        }
                    }
                }
                $type = $redis->type($skey); // int
                $ttl = $redis->ttl($skey);
                $text = (strlen($text) > $len ? substr($text, 0, $len) . '..' : $text);
                $title = $skey . ' [' . $typenames[$type] . '][ttl:' . $ttl . ']';
                echo '<li', ($curkey == $skey ? ' class="active"' : ''), '>', $i, '.<a href="?action=showedit&db=', $dbname, '&key=', urlencode($skey), '&token=', $token, '" title="', $title, '">', $text, '</a></li>';
                //flush(); 服务器启用了PHP缓存，这里要注释，否则会版面会错乱
            }
            $i++;
        }
    }
}

/**
 * scan方法获取键名
 * <br />2.8.0以上版本适用
 * @param Redis $redis
 * @param integer $row_start
 * @param integer $row_end
 * @param string $curkey 当前输入的键名
 * @param string $key_prefix
 * @param string $dbname
 * @param string $token
 * @version 1.0 <2015-10-8> SoChishun Added.
 */
function show_keys_scan($redis, $row_start, $row_end, $curkey, $key_prefix, $dbname, $token) {
    $i = 0;
    $text = '';
    $title = '';
    $is_break = false;
    $typenames = array(0 => 'NOT_FOUND', 1 => 'STRING', 2 => 'SET', 3 => 'LIST', 4 => 'REDIS_ZSET', 5 => 'HASH');
    $it = NULL; /* Initialize our iterator to NULL */
    $redis->setOption(Redis::OPT_SCAN, Redis::SCAN_RETRY); /* retry when we get no keys back */
    while ($akeys = $redis->scan($it)) {
        if ($is_break) {
            break;
        }
        foreach ($akeys as $skey) {
            if ($i > $row_end) {
                $is_break = TRUE;
                break;
            }
            if ($i >= $row_start) {
                $len = 20 - strlen($i);
                $text = $skey;
                if ($key_prefix) {
                    foreach ($key_prefix as $prefix) {
                        if (0 === strpos($skey, $prefix)) {
                            $key_prefix_len = strlen($prefix);
                            $text = ($key_prefix_len > 0) ? substr($skey, $key_prefix_len) : $skey;
                            break;
                        }
                    }
                }
                $type = $redis->type($skey); // int
                $ttl = $redis->ttl($skey);
                $text = (strlen($text) > $len ? substr($text, 0, $len) . '..' : $text);
                $title = $skey . ' [' . $typenames[$type] . '][ttl:' . $ttl . ']';
                echo '<li', ($curkey == $skey ? ' class="active"' : ''), '>', $i, '.<a href="?db=', $dbname, '&action=showedit&key=', urlencode($skey), '&token=', $token, '" title="', $skey, ' [', $typenames[$type], ']">', $text, '</a></li>';
            }
            $i++;
        }
        // echo "No more keys to scan!\n";
    }
}

/**
 * 显示命令表单
 * @param string $curDbName
 * @param string $cmd
 * @param string $token
 * @version 2017-10-27
 */
function show_command_form($curDbName, $cmd, $token) {
    echo '<fieldset><legend>命令行<a href="https://github.com/phpredis/phpredis" target="_blank" title="打开命令帮助手册">(?)</a><a href="#" class="lnk-switch">[-]</a></legend><div class="fieldset_content"><form>
            <textarea name="cmd" cols="130" rows="2" required="required" placeholder="输入Redis命令">', $cmd, '</textarea>
            <br /><button type="submit">执行</button> <button type="reset">重置</button>
            <input type="hidden" name="db" value="', $curDbName, '" />
            <input type="hidden" name="token" value="', $token, '" />
            <input type="hidden" name="action" value="docommand" />
        </form></div></fieldset>';
}

/**
 * 显示编辑表单
 * @param Redis $redis Redis实例对象
 * @param string $curKey 键名
 * @param string $dbname
 * @version 1.0 <2015-8-13> SoChishun Added.
 */
function show_edit_form($redis, $curKey, $dbname, $token) {
    echo '<fieldset><legend>编辑项目</legend>';
    echo '<div><strong>键名：</strong>';
    echo '<input type="text" value="', $curKey, '" size="60" data-value="', $curKey, '" />';
    echo '<a href="?db=', $dbname, '&action=dorename&key=', urlencode($curKey), '&newkey=varnewkey&token=' . $token . '" onclick="return fn_rename_submit(this)">[重命名]</a>';
    echo '<a href="?db=', $dbname, '&action=dodelete&key=', urlencode($curKey), '&token=' . $token . '" onclick="return confirm(\'您确定要删除吗?此操作不可恢复!\');">[删除]</a>';
    echo '</div>';
    if ($redis->exists($curKey)) {
        $val = false;
        $ttl = $redis->ttl($curKey);
        $ttlstr = '<span title="-1表示未设置TTL,-2表示键不存在">剩余过期时间：' . $ttl . ($ttl < 0 ? '' : '秒') . '</span>';
        $type = $redis->type($curKey); // int
        $fmt = strtolower(input('fmt')); // 数据结果格式转换
        $btn_json_viewer = false === strpos($fmt, 'json') ? '' : ' <a href="http://www.json.cn/" target="_blank">[JSON美化]</a>';
        switch ($type) {
            case Redis::REDIS_HASH:
                $val = $redis->hGetAll($curKey);
                $tdirect = input('tdirect');
                $is_vidrect = $tdirect == 'v';
                echo '<div><a href="#" class="lnk-show" title="显示隐藏表格行">[+]</a> 类型：REDIS_HASH ', $ttlstr, ' 项目个数：', count($val), '</div>';
                echo '<div class="content">';
                // 横向或竖向表格
                $btns = '<a href="?action=showedit&key=' . $curKey . '&fmt=' . $fmt . '&tdirect=h&token=' . $token . '">[横向]</a><a href="?action=showedit&key=' . $curKey . '&fmt=' . $fmt . '&tdirect=v&token=' . $token . '">[竖向]</a>';
                echo '<div>表格类型：', $btns, '</div>';
                echo '<table cellspacing="0" class="grid">';
                $btn_update = '<a href="?db=' . $dbname . '&action=dohset&key=' . $curKey . '&field=varfield&value=varvalue&token=' . $token . '" onclick="return fn_hset_submit(this);" title="更新字段值">[u]</a>';
                $i = 0;
                foreach ($val as $th => $td) {
                    if ($is_vidrect) {
                        echo '<tr><th>', $btn_update, '<a href="?db=' . $dbname . '&action=dohdel&key=' . $curKey . '&field=', $th, '&token=', $token, '" title="删除字段" onclick="return confirm(\'确定要删除该字段吗?此操作不可恢复!\')">[x]</a><a href="#" class="lnk-hide-v" title="隐藏当前表格行">[-]</a>[', $i, '] ', $th, '<input type="hidden" name="field" value="', $th, '" /></th></tr>';
                        echo '<tr class="unhover"><td><input type="text" name="value" value="', htmlspecialchars($td), '" size="60" /></td></tr>';
                    } else {
                        echo '<tr><th>', $btn_update, '<a href="?db=' . $dbname . '&action=dohdel&key=' . $curKey . '&field=', $th, '&token=', $token, '" title="删除字段" onclick="return confirm(\'确定要删除该字段吗?此操作不可恢复!\')">[x]</a><a href="#" class="lnk-hide" title="隐藏当前表格行">[-]</a>[', $i, '] ', $th, '<input type="hidden" name="field" value="', $th, '" /></th><td><input type="text" name="value" value="', (htmlspecialchars($td)), '" size="60" /></td></tr>';
                    }
                    $i++;
                }
                // 新增字段
                $btn_update = '<a href="?db=' . $dbname . '&action=dohset&key=' . $curKey . '&field=varfield&value=varvalue&token=' . $token . '" onclick="return fn_hset_submit(this);" title="更新字段值">[save]</a>';
                if ($is_vidrect) {
                    echo '<tr><td>新增项目</td></tr>';
                    echo '<tr><th>', $btn_update, '<input type="text" name="field" /></th></tr>';
                    echo '<tr class="unhover"><td><input type="text" name="value" size="60" /></td></tr>';
                } else {
                    echo '<tr><td colspan="2">新增项目</td></tr>';
                    echo '<tr><th>', $btn_update, '<input type="text" name="field" /></th><td><input type="text" name="value" size="60" /></td></tr>';
                }
                // 结果数据解析类型
                $btns = '<a href="?action=showedit&key=' . $curKey . '&fmt=json&tdirect=' . $tdirect . '&token=' . $token . '">[JSON]</a><a href="?action=showedit&key=' . $curKey . '&fmt=varexport&tdirect=' . $tdirect . '&token=' . $token . '">[VAR_EXPORT]</a>';
                echo '<tr class="unhover"><td', ($is_vidrect ? '' : ' colspan="2"'), '>显示格式：', $btns, $btn_json_viewer, '</td></tr>';
                if ($fmt == 'varexport') {
                    echo '<tr class="unhover"><td', ($is_vidrect ? '' : ' colspan="2"'), '><textarea style="width:99%" cols="90" rows="27">', var_export($val, true), '</textarea></td></tr>';
                } else {
                    echo '<tr class="unhover"><td', ($is_vidrect ? '' : ' colspan="2"'), '>', json_encode($val), '</td></tr>';
                }
                echo '</table></div>';
                break;
            case Redis::REDIS_LIST:
                $val = $redis->lRange($curKey, 0, -1);
                echo '<div>类型：REDIS_LIST ', $ttlstr, ' 项目个数：', count($val), '</div>';
                echo '<div class="content"><table cellspacing="0" class="grid">';
                $i=0;
                foreach ($val as $td) {
                    echo '<tr><td><a href="#" class="lnk-hide" title="隐藏当前表格行">[-]</a>[',$i,']</td><td><input type="text" value="', htmlspecialchars($td), '" /></td></tr>';
                    $i++;
                }
                // 新增值     
                echo '<tr><td colspan="2">新增项目</td></tr>';
                echo '<tr><th><a href="?db=' . $dbname . '&action=dohset&key=' . $curKey . '&field=varfield&value=varvalue&token=' . $token . '" onclick="return fn_hset_submit(this);" title="更新字段值">[save]</a>在<input type="text" />之后</th><td><input type="text" /></td></tr>';
                echo '<tr class="unhover"><td colspan="2">', htmlspecialchars(json_encode($val)), '</td></tr>';
                echo '</table></div>';
                break;
            case Redis::REDIS_NOT_FOUND:
                echo '<div>类型：REDIS_NOT_FOUND</div>';
                break;
            case Redis::REDIS_SET:
                $val = $redis->sMembers($curKey);
                echo '<div>类型：REDIS_SET ', $ttlstr, ' 项目个数：', count($val), '</div>';
                echo '<div class="content"><table cellspacing="0" class="grid">';
                echo '<tr><th>值</th></tr>';
                $i=0;
                foreach ($val as $td) {
                    echo '<tr><td><a href="#" class="lnk-hide" title="隐藏当前表格行">[-]</a>[',$i,'] ', htmlspecialchars($td), '</td></tr>';
                    $i++;
                }
                echo '<tr class="unhover"><td>', htmlspecialchars(json_encode($val)), '</td></tr>';
                echo '</table></div>';
                break;
            case Redis::REDIS_STRING:
                echo '<div>类型：REDIS_STRING ', $ttlstr, '</div>';
                $val = $redis->get($curKey);
                echo '<form>';
                echo '<div class="content"><table cellspacing="0" class="grid">';
                // 结果数据解析类型
                $btns = '';
                if (0 === strpos($val, '{')) {
                    $btns .= '<a href="?action=showedit&key=' . $curKey . '&fmt=json&token=' . $token . '">[JSON]</a>';
                }
                if (0 !== strpos($val, '{') && strpos($val, ':')) {
                    $btns .= '<a href="?action=showedit&key=' . $curKey . '&fmt=unserialize&token=' . $token . '">[UNSERIALIZE]</a><a href="?action=showedit&key=' . $curKey . '&fmt=unserialize-json&token=' . $token . '">[UNSERIALIZE-JSON]</a>';
                }
                echo '<tr class="unhover"><td>显示格式：', $btns, '</td></tr>';
                echo '<tr class="unhover"><td><textarea cols="90" rows="9" name="value" style="width:99%">', htmlspecialchars($val), '</textarea></td></tr>';
                if ($fmt) {
                    echo '<tr class="unhover"><td>', $fmt, $btn_json_viewer, '<br /><textarea cols="90" rows="30" style="width:99%">';
                    if ($fmt == 'json') {
                        var_export(json_decode($val, true));
                    } else if ($fmt == 'unserialize') {
                        var_export(unserialize($val));
                    } else if ($fmt == 'unserialize-json') {
                        echo json_encode(unserialize($val));
                    }
                    echo '</textarea></td></tr>';
                }
                echo '</table></div>';
                echo '<button type="submit">保存</button> <button type="reset">重置</button>';
                echo '<input type="hidden" name="db" value="', $dbname, '" />';
                echo '<input type="hidden" name="token" value="', $token, '" />';
                echo '<input type="hidden" name="key" value="', $curKey, '" />';
                echo '<input type="hidden" name="action" value="doset" />';
                echo '</form>';
                break;
            case Redis::REDIS_ZSET:
                $val = $redis->zRange($curKey, 0, -1, true);
                echo '<div>类型：REDIS_ZSET ', $ttlstr, ' 项目个数：', count($val), '</div>';
                echo '<div class="content"><table cellspacing="0" class="grid">';
                echo '<tr><th>值</th><th>排序</th></tr>';
                $i=0;
                foreach ($val as $td => $sort) {
                    echo '<tr><td><a href="#" class="lnk-hide" title="隐藏当前表格行">[-]</a>[',$i,'] ', htmlspecialchars($td), '</td><td>', $sort, '</td></tr>';
                    $i++;
                }
                echo '<tr class="unhover"><td colspan="2">', htmlspecialchars(json_encode($val)), '</td></tr>';
                echo '</table></div>';
                break;
        }
    } else {
        echo '<div style="margin:10px 0;" class="red">键名不存在!</div>';
    }
    echo '</fieldset>';
}

/**
 * 显示新增表单
 * @param string $dbname
 * @param string $type
 * @param string $token
 * @version 1.0 <2015-8-13> SoChishun Added.
 * @version 2.0 <2017-10-12> SoChishun 重构
 */
function show_add_form($dbname, $type, $token) {
    $actions = array('string' => 'doset', 'hash' => 'dohset', 'list' => 'dorpush', 'set' => 'dosadd', 'zset' => 'dozadd');
    echo '<fieldset><legend>新增', ucwords($type), '键</legend>';
    echo '<form method="post">';
    echo '<div class="content"><table cellspacing="0" class="grid">';
    echo '<tr><th>键名：</th><td><input type="text" name="key" /></td></tr>';
    if ($type == 'hash') {
        echo '<tr><th>字段：</th><td><input type="text" name="field" /></td></tr>';
    } else if ($type == 'zset') {
        echo '<tr><th>Score：</th><td><input type="number" name="score" value="0" /></td></tr>';
    }
    echo '<tr><th>值：</th><td>';
    echo '<textarea name="value" cols="90" rows="9"></textarea></td></tr>';
    echo '</table></div>';
    echo '<button type="submit">保存</button> <button type="reset">重置</button>';
    echo '<input type="hidden" name="action" value="', $actions[$type], '" />';
    echo '<input type="hidden" name="db" value="', $dbname, '" />';
    echo '<input type="hidden" name="token" value="', $token, '" />';
    echo '</form>';
    echo '</fieldset>';
}

/**
 * 显示查询表单
 * @param Redis $redis
 * @param string $searchKey
 * @param string $key_prefix
 * @param string $dbname
 * @param string $token
 * @return type
 * @version 1.0 <2015-8-14> SoChishun Added.
 * @version 2.0 <2017-10-10> SoChishun 重构，把action_search合并入show_search_form();
 */
function show_search_form($redis, $searchKey, $prefix, $dbname, $token) {
    echo '<h3>搜索</h3>';
    echo '<form method="post">';
    echo '<div class="content"><table cellspacing="0" class="grid">';
    echo '<tr class="unhover"><th>键名：</th><td><p class="red">支持模糊搜索，如：*words*</p><input type="text" name="key" value="', input('key', $prefix), '" size="90" /></td></tr>';
    echo '</table></div>';
    echo '<button type="submit">立即搜索</button>';
    echo '<input type="hidden" name="action" value="search" />';
    echo '<input type="hidden" name="db" value="', $dbname, '" />';
    echo '<input type="hidden" name="token" value="', $token, '" />';
    echo '</form>';

    $data = $_REQUEST;
    if (empty($data['key'])) {
        return;
    }
    $searchKey = $data['key'];

    $delkeys = input('delkeys'); // 批量删除功能 2017-10-24
    if ($delkeys) {
        if ($delkeys == '*') {
            $akeys = $redis->keys($searchKey);
            $count = count($akeys);
            $redis->delete($akeys);
        } else {
            $akeys = explode(',', $delkeys);
            $count = count($akeys);
            $redis->delete($akeys);
        }
        redirect(true, '<br /><strong class="red">共删除' . $count . '条记录!</strong>', "?key=$searchKey&action=search&db=$dbname&token=$token");
        return;
    }
    // 查询结果
    $akeys = $redis->keys($searchKey);
    if (!$akeys) {
        echo '<div style="margin:5px 0;">查询结果：</div><strong class="red">没有找到符合条件的记录! </strong><a href="#" onclick="location.reload()">[刷新]</a>';
        return;
    }
    $i = 0;
    $n = count($akeys);
    echo '<div style="margin-top:5px;">查询结果[' . $n . ']：</div>';
    echo '<div><button type="button" id="btn-del-checked" data-url="?key=', $searchKey, '&action=search&db=', $dbname, '&delkeys=varkey&token=', $token, '">删除所选</button> <button type="button" id="btn-del-all" data-url="?key=', $searchKey, '&action=search&db=', $dbname, '&delkeys=*&token=', $token, '">全部删除</button></div>';
    echo '<div class="search_result">';
    foreach ($akeys as $skey) {
        echo '<div><input type="checkbox" value="', $skey, '" />', $i, '.<a href="?db=', $dbname, '&action=showedit&key=', urlencode($skey), '&token=', $token, '" title="', $skey, '">', $skey, '</a></div>';
        $i++;
    }
    echo '</div>';
}

/**
 * 删除键操作
 * @param Redis $redis
 * @param string $curKey
 * @param string $dbname
 * @param string $token
 * @return string
 * @version 1.0 <2015-8-13> SoChishun Added.
 */
function action_delete($redis, $curKey, $dbname, $token) {
    if (!$curKey) {
        redirect(false, '删除失败: 键名不存在!');
    } else {
        $redis->delete($curKey);
        redirect(true, "键 [$curKey] 删除成功!", "?db=$dbname&token=$token");
    }
}

/**
 * 重命名键名
 * @param type $redis
 * @param type $curKey
 * @param type $newKey
 * @param type $dbname
 * @param string $token
 * @version 1.0 2017-10-12 SoChishun Added.
 */
function action_dorename($redis, $curKey, $newKey, $dbname, $token) {
    if (empty($curKey) || empty($newKey)) {
        exit('参数有误!');
    }
    $result = $redis->rename($curKey, $newKey);
    $status = false !== $result;
    $msg = $status ? '重命名成功' : '重命名失败';
    $url = "?action=showedit&db=$dbname&key=" . ($status ? $newKey : $curKey) . "&token=$token";
    redirect($status, $msg, $url);
}

/**
 * hash hset操作
 * @param type $redis
 * @param type $curKey
 * @param type $field
 * @param type $value
 * @param type $dbname
 * @param string $token
 * @version 1.0 2017-10-12 SoChishun Added.
 */
function action_dohset($redis, $curKey, $field, $value, $dbname, $token) {
    if (empty($curKey) || empty($field)) {
        exit('参数有误!');
    }
    $result = $redis->hSet($curKey, $field, $value);
    $status = false !== $result;
    $msg = $status ? '操作成功' : '程序错误';
    $url = "?action=showedit&db=$dbname&key=$curKey&token=$token";
    redirect($status, $msg, $url);
}

/**
 * hash hdel操作
 * @param type $redis
 * @param type $curKey
 * @param type $field
 * @param type $dbname
 * @param string $token
 * @version 1.0 2017-10-12 SoChishun Added.
 */
function action_dohdel($redis, $curKey, $field, $dbname, $token) {
    if (empty($curKey) || empty($field)) {
        exit('参数有误!');
    }
    $result = $redis->hDel($curKey, $field);
    $status = false !== $result;
    $msg = $status ? '操作成功' : '程序错误';
    $url = "?action=showedit&db=$dbname&key=$curKey&token=$token";
    redirect($status, $msg, $url);
}

/**
 * string set 操作
 * @param type $redis
 * @param type $curKey
 * @param type $field
 * @param type $dbname
 * @param string $token
 * @version 1.0 2017-10-12 SoChishun Added.
 */
function action_doset($redis, $curKey, $value, $dbname, $token) {
    if (empty($curKey) || empty($value)) {
        exit('参数有误!');
    }
    $result = $redis->set($curKey, $value);
    $status = false !== $result;
    $msg = $status ? '操作成功' : '程序错误';
    $url = "?action=showedit&db=$dbname&key=$curKey&token=$token";
    redirect($status, $msg, $url);
}

/**
 * list rpush操作
 * @param type $redis
 * @param type $curKey
 * @param type $value
 * @param type $dbname
 * @param string $token
 * @version 1.0 2017-10-12 SoChishun Added.
 */
function action_dorpush($redis, $curKey, $value, $dbname, $token) {
    if (empty($curKey) || empty($value)) {
        exit('参数有误!');
    }
    $result = $redis->rPush($curKey, $value);
    $status = false !== $result;
    $msg = $status ? '操作成功' : '程序错误';
    $url = "?action=showedit&db=$dbname&key=$curKey&token=$token";
    redirect($status, $msg, $url);
}

/**
 * set sadd操作
 * @param type $redis
 * @param type $curKey
 * @param type $value
 * @param type $dbname
 * @param string $token
 * @version 1.0 2017-10-12 SoChishun Added.
 */
function action_dosadd($redis, $curKey, $value, $dbname, $token) {
    if (empty($curKey) || empty($value)) {
        exit('参数有误!');
    }
    $result = $redis->sAdd($curKey, $value);
    $status = false !== $result;
    $msg = $status ? '操作成功' : '程序错误';
    $url = "?action=showedit&db=$dbname&key=$curKey&token=$token";
    redirect($status, $msg, $url);
}

/**
 * zset zadd操作
 * @param type $redis
 * @param type $curKey
 * @param type $score
 * @param type $value
 * @param type $dbname
 * @param string $token
 * @version 1.0 2017-10-12 SoChishun Added.
 */
function action_dozadd($redis, $curKey, $score, $value, $dbname) {
    if (empty($curKey) || empty($value)) {
        exit('参数有误!');
    }
    $result = $redis->zAdd($curKey, $score, $value);
    $status = false !== $result;
    $msg = $status ? '操作成功' : '程序错误';
    $url = "?action=showedit&db=$dbname&key=$curKey&token=$token";
    redirect($status, $msg, $url);
}

/**
 * 执行命令
 * @param Redis $redis
 * @param string $dbname
 */
function action_command($redis, $dbname, $cmd) {
    $cmdArr = array_filter(explode(' ', $cmd));
    $cmdArrCount = count($cmdArr);
    $cmdAction = strtolower($cmdArr[0]);
    if ($cmdArrCount == 0) {
        return;
    }
    $forbitAct = ['flushdb', 'flushall'];
    if (in_array(strtolower($cmdArr[0]), $forbitAct)) {
        echo '<span style="color:#F00">夭寿哦，这种命令你也敢在这里随便执行！准备要删库跑路吗！</span>';
        return;
    }
    switch ($cmdArrCount) {
        case 1:
            $result = $redis->rawcommand($cmdArr[0]);
            break;
        case 2:
            $result = $redis->rawcommand($cmdArr[0], $cmdArr[1]);
            break;
        case 3:
            $result = $redis->rawcommand($cmdArr[0], $cmdArr[1], $cmdArr[2]);
            break;
        case 4:
            $result = $redis->rawcommand($cmdArr[0], $cmdArr[1], $cmdArr[2], $cmdArr[3]);
            break;
        case 5:
            $result = $redis->rawcommand($cmdArr[0], $cmdArr[1], $cmdArr[2], $cmdArr[3], $cmdArr[4]);
            break;
    }
    var_export($result);
}

//==================================公用函数=================================

/**
 * 解析服务器配置中的IP和Port
 * @param array $server
 * @return array array('host'=>'','port'=>'')
 * @version 1.0 2017-10-13 SoChishun Added.
 */
function parse_server_host_and_port($server) {
    $host = $server['host'];
    $port = $server['port'];
    if (isset($server['rinetd'])) {
        $rinetdcfg = $server['rinetd'];
        if (isset($rinetdcfg['host']) && !empty($rinetdcfg['host'])) {
            $host = $rinetdcfg['host'];
            // 如果服务器IP和端口映射服务器一样,则自动忽略端口映射配置,提高性能 2017-10-13
            if ($host == get_host_ip()) {
                $host = $server['host'];
            } else {
                if (isset($rinetdcfg['port']) && !empty($rinetdcfg['port'])) {
                    $port = $rinetdcfg['port'];
                }
            }
        }
    }
    return array($host, $port);
}

/**
 * 解析配置中的本地配置文件数据
 * @param array $server
 * @return array
 * @version 2017-12-6
 */
function parse_server_localconffile(array $server) {
    // 如果有本地配置文件,则以本地配置文件为主
    $confFileInfo = isset($server['conf_file']['path']) ? $server['conf_file'] : false;
    if ($confFileInfo && file_exists($confFileInfo['path'])) {
        $confContent = require_once $confFileInfo['path'];
        if (is_array($confContent)) {
            if (empty($confFileInfo['map'])) {
                $confData = $confContent;
            } else {
                $confMap = $confFileInfo['map'];
                $confData = [];
                foreach ($confMap as $key => $value) {
                    if (array_key_exists($value, $confContent)) {
                        $confData[$key] = $confContent[$value];
                        unset($confContent[$value]);
                    }
                }
            }
            $server = array_merge($server, $confData);
            unset($server['rinetd']);
            unset($server['conf_file']);
        }
    }
    return $server;
}

/**
 * 服务器配置通用类
 * @version 2017-12-19 Added.
 */
class ServerConfUtil {

    /**
     * 解析服务器配置中的IP和Port
     * @param array $server
     * @param string $host_ip
     * @return array array('host'=>'','port'=>'')
     * @version 1.0 2017-10-13 SoChishun Added.
     */
    public static function parseHostAndPortFromServer(array $server) {
        $host = $server['host'];
        $port = $server['port'];
        if (isset($server['rinetd'])) {
            $rinetdcfg = $server['rinetd'];
            if (isset($rinetdcfg['host']) && !empty($rinetdcfg['host'])) {
                $host = $rinetdcfg['host'];
                // 返回服务器IP, 参考：SERVER_NAME 和HTTP_HOST的区别 (http://blog.sina.com.cn/s/blog_6d96d3160100q39x.html)
                $host_ip = gethostbyname($_SERVER['SERVER_NAME']);
                // 如果服务器IP和端口映射服务器一样,则自动忽略端口映射配置,提高性能 2017-10-13
                if ($host == $host_ip) {
                    $host = $server['host'];
                } else {
                    if (isset($rinetdcfg['port']) && !empty($rinetdcfg['port'])) {
                        $port = $rinetdcfg['port'];
                    }
                }
            }
        }
        return array($host, $port);
    }

    /**
     * 解析配置中的本地配置文件数据
     * @param array $server
     * @return array
     * @version 2017-12-6
     */
    public static function parseServerFromFile(array $server) {
        // 如果有本地配置文件,则以本地配置文件为主
        $confFileInfo = isset($server['conf_file']['path']) ? $server['conf_file'] : false;
        if ($confFileInfo && file_exists($confFileInfo['path'])) {
            $confContent = require_once $confFileInfo['path'];
            if (is_array($confContent)) {
                if (empty($confFileInfo['map'])) {
                    $confData = $confContent;
                } else {
                    $confMap = $confFileInfo['map'];
                    $confData = [];
                    foreach ($confMap as $key => $value) {
                        if (array_key_exists($value, $confContent)) {
                            $confData[$key] = $confContent[$value];
                            unset($confContent[$value]);
                        }
                    }
                }
                $server = array_merge($server, $confData);
                unset($server['rinetd']);
                unset($server['conf_file']);
            }
        }
        return $server;
    }

    /**
     * 获取配置内容中的当前数据库服务器配置
     * @param type $config
     * return array
     * @version 2017-12-15
     */
    public static function getCurrentServer($config) {
        $curServId = $config['current_server']; // 当前服务器名称
        if (!$curServId || !isset($config['server'][$curServId])) {
            exit('服务器[' . $curServId . ']不存在!');
        }
        $server = $config['server'][$curServId]; // 当前数据库服务器配置
        // 如果有本地配置文件,则以本地配置文件内容覆盖页面配置
        $server = self::parseServerFromFile($server);
        $server['current_id'] = $curServId;
        return $server;
    }

}
